home *** CD-ROM | disk | FTP | other *** search
- /*------------------------------------------------------
- #
- # Program: Tutor3D™
- #
- # Copyright © 1991 Lincoln Lydick
- # All Rights Reserved.
- #
- #
- Include these libraries (for THINK C):
- MacTraps
- SANE
-
- Note:
- The procedures "RotateObject()" and "Point2Screen()"
- significantly slow this program because THINK C creates a
- JSR to some extra glue code in order to multiply and divide
- long words. Therefore both procs are written in assembly,
- however the C equivalent is provided in comments above.
- Simply replace the asm {} statement with the C code if you
- prefer.
-
- ------------------------------------------------------*/
-
- #include "SANE.h"
-
- #define kMaxObjects 6 /*num. objects*/
- #define kMinutes 4 /*minutes per deqree*/
- #define kProjDistance 450 /*distance to proj. plane*/
- #define kWidth 500 /*width of window*/
- #define kHeight 280 /*height of window*/
- #define kMoveUpKey 0x100000 /*'q' key = move up*/
- #define kMoveDnKey 0x200000 /*'w' key = move down*/
- #define kOriginH (kWidth/2) /*center of window */
- #define kOriginV (kHeight/2) /*ditto */
- #define kMapRowBytes (((kWidth+15)/16)*2)
- #define kNotBWAlertNum 256 /* ALRT id for monitor bit depth > 1 */
-
- /* Define macros so MoveTo() & LineTo() accept Points.*/
- #define QuickMoveTo(pt) asm{move.l pt, gOffPort.pnLoc}
- #define QuickLineTo(pt) asm{move.l pt, -(sp)}asm {_LineTo}
-
- enum ObjectType {cube, pyramid};
- typedef struct {short x, y, z;
- } Point3D; /* struct for a 3 dimensional point.*/
-
- typedef struct {
- Point3D pt3D;
- short angle, sine, cosine;
- } ViewerInfo; /* struct for viewer's position.*/
-
- typedef struct {
- enum ObjectType objType;
- Point3D pt3D;
- short angle, halfWidth, height;
- Boolean rotates, moves;
- } ObjectInfo; /* struct for an object.*/
-
- ViewerInfo gViewer;
- Point3D gDelta;
- Point gMouse, gVertex[8];
- WindowPtr gWindow;
- BitMap gBitMap;
- GrafPort gOffPort;
- Rect gVisRect, gWindowRect;
- ObjectInfo gObject[kMaxObjects];
- short gVelocity, gSineTable[(90*kMinutes)+1];
- KeyMap gKeys;
-
- /****************************************************/
- /*
- /* Assign parameters to a new object (a cube or pyramid).
- /*
- /****************************************************/
- static void NewObject(short index, enum ObjectType theType, short width, short height,
- Boolean rotates, Boolean moves, short positionX, short positionY, short positionZ)
- {
- register ObjectInfo *obj;
-
- obj = &gObject[index];
- obj->angle = 0;
- obj->objType = theType;
- obj->halfWidth = width/2;
- obj->height = height;
- obj->rotates = rotates;
- obj->moves = moves;
- obj->pt3D.x = positionX;
- obj->pt3D.y = positionY;
- obj->pt3D.z = positionZ;
- }
-
- /****************************************************/
- /*
- /* Initialize of all our globals, build the trig table, set up an
- /* offscreen buffer, create a new window, and initialize all
- /* the objects to be drawn.
- /*
- /****************************************************/
- static void Initialize(void)
- {
- extended angle;
- short i;
-
- InitGraf(&thePort);
- InitFonts();
- InitWindows();
- InitMenus();
- TEInit();
- InitDialogs(0L);
- InitCursor();
- FlushEvents(everyEvent, 0);
- SetCursor(*GetCursor(crossCursor));
-
- if ((*(*GetMainDevice())->gdPMap)->pixelSize > 1)
- {
- InitCursor();
- StopAlert(kNotBWAlertNum, NULL); /* tell user to switch to B&W.*/
- ExitToShell();
- }
-
- /* create a table w/ the values of sine from 0-90.*/
- for (i=0, angle=0.0; i<=90*kMinutes; i++, angle+=0.017453292/kMinutes)
- gSineTable[i] = sin(angle)*1000;
-
- /* give the viewer an initial direction and position…*/
- gViewer.angle = gViewer.sine = gViewer.pt3D.x = gViewer.pt3D.y = 0;
- gViewer.cosine = 999;
- gViewer.pt3D.z = 130;
-
- /* create some objects (0 to kMaxObjects-1).*/
- NewObject(0, cube, 120, 120, false, false, -150, 600, 0);
- NewObject(1, cube, 300, 300, true, false, -40, 1100, 60);
- NewObject(2, cube, 40, 10, true, true, 0, 500, 0);
- NewObject(3, pyramid, 160, 160, false, false, 200, 700, 0);
- NewObject(4, pyramid, 80, -80, true, false, 200, 700, 240);
- NewObject(5, pyramid, 60, 60, false, false, -40, 1100, 0);
-
- SetRect(&gBitMap.bounds, 0, 0, kWidth, kHeight);
- SetRect(&gWindowRect, 6, 45, kWidth+6, kHeight+45);
- SetRect(&gVisRect, -150, -150, 650, 450);
- gWindow = NewWindow(0L, &gWindowRect, "\pTutor3D™", true, 0, (Ptr)-1, false, 0);
-
- /* make an offscreen bitmap and port…*/
- gBitMap.rowBytes = kMapRowBytes;
- gBitMap.baseAddr = NewPtr(kHeight*kMapRowBytes);
- OpenPort(&gOffPort);
- SetPort(&gOffPort);
- SetPortBits(&gBitMap);
- PenPat(white);
- }
-
- /****************************************************/
- /*
- /* Return the sine and cosine values for an angle.
- /*
- /****************************************************/
- static void GetTrigValues(register short *angle, register short *sine, register short *cosine)
- {
- if (*angle >= 360*kMinutes)
- *angle -= 360*kMinutes;
- else if (*angle < 0)
- *angle += 360*kMinutes;
-
- if (*angle <= 90*kMinutes)
- { *sine = gSineTable[*angle];
- *cosine = gSineTable[90*kMinutes - *angle];
- }
- else if (*angle <= 180*kMinutes)
- { *sine = gSineTable[180*kMinutes - *angle];
- *cosine = -gSineTable[*angle - 90*kMinutes];
- }
- else if (*angle <= 270*kMinutes)
- { *sine = -gSineTable[*angle - 180*kMinutes];
- *cosine = -gSineTable[270*kMinutes - *angle];
- }
- else
- { *sine = -gSineTable[360*kMinutes - *angle];
- *cosine = gSineTable[*angle - 270*kMinutes];
- } }
-
- /****************************************************/
- /*
- /* Increment an objects angle and find the sine and cosine
- /* values. If the object moves, assign a new x,y position for
- /* it as well. Finally, rotate the object's base around the z
- /* axis and translate it to the correct position based on delta.
- /*
- /* register Point *vertex; short i;
- /*
- /* for (i = 0; i < 4; i++)
- /* { vertex = &gVertex[i]; savedH = vertex->h;
- /* vertex->h=((long)savedH*cosine/1000 -
- /* (long)vertex->v*sine/1000)+gDelta.x;
- /* vertex->v=((long)savedH*sine/1000 +
- /* (long)vertex->v*cosine/1000)+gDelta.y;
- /* }
- /****************************************************/
- static void RotateObject(register ObjectInfo *object)
- {
- Point tempPt;
- short sine, cosine;
-
- object->angle += (object->objType == pyramid) ? -8*kMinutes : 2*kMinutes;
- GetTrigValues(&object->angle, &sine, &cosine);
- if (object->moves)
- { object->pt3D.x += sine*20/1000; /*[EQ.1]*/
- object->pt3D.y += cosine*-20/1000; /*[EQ.2]*/
- }
-
- asm { moveq #3, d2 ; loop counter
- lea gVertex, a0 ; our array of points
- loop: move.l (a0), tempPt ; ie., tempPt = gVertex[i];
- move.w cosine, d0
- muls tempPt.h, d0 ; tempPt.h * cosine
- divs #1000, d0 ; divide by 1000
- move.w sine, d1
- muls tempPt.v, d1 ; tempPt.v * sine
- divs #1000, d1 ; divide by 1000
- sub.w d1, d0 ; subtract the two
- add.w gDelta.x, d0 ; now translate x
- move.w d0, OFFSET(Point, h)(a0); save new h
-
- move.w sine, d0
- muls tempPt.h, d0 ; tempPt.h * sine
- divs #1000, d0 ; divide by 1000
- move.w cosine, d1
- muls tempPt.v, d1 ; tempPt.v * cosine
- divs #1000, d1 ; divide by 1000
- add.w d1, d0 ; add em up
- add.w gDelta.y, d0 ; now translate y
- move.w d0, OFFSET(Point, v)(a0); save new v
- addq.l #4, a0 ; next vertex address
- dbra d2, @loop ; loop
- }
- }
-
- /****************************************************/
- /*
- /* Rotate a point around the z axis and find it's location in 2d
- /* space using 2pt perspective.
- /*
- /* saved = pt->h; /* saved is defined as a short.*/
- /* pt->h = (long)saved*gViewer.cosine/1000 -
- /* (long)pt->v*gViewer.sine/1000; /*[EQ.6]*/
- /* pt->v = (long)saved*gViewer.sine/1000 +
- /* (long)pt->v*gViewer.cosine/1000; /*[EQ.7]*/
- /* /*[EQ.8 & 9]*/
- /* if ((saved = pt->v) <= 0) saved = 1;/*never <= 0*/
- /* pt->h = (long)pt->h*kProjDistance/saved+kOriginH;
- /* pt->v = (long)gDelta.z*kProjDistance/saved+kOriginV;
- /*
- /****************************************************/
- static void Point2Screen(register Point *pt)
- { asm {
- move.w gViewer.cosine, d0 ; [EQ.6]
- muls OFFSET(Point, h)(pt), d0; pt.h * cosine
- divs #1000, d0 ; divide by 1000
- move.w gViewer.sine, d1
- muls OFFSET(Point, v)(pt), d1; pt.v * sine
- divs #1000, d1 ; divide by 1000
- sub.w d1, d0 ; subtract, yields horizontal
- move.w gViewer.sine, d1 ; [EQ.7]
- muls OFFSET(Point, h)(pt), d1; pt.h * sine
- divs #1000, d1 ; divide by 1000
- move.w gViewer.cosine, d2
- muls OFFSET(Point, v)(pt), d2; pt.v * cosine
- divs #1000, d2 ; divide by 1000
- add.w d2, d1 ; add, yields vertical
- bgt @project ; if (vertical<=0)…
- moveq #1, d1 ; then vertical=1
-
- project: muls #kProjDistance, d0 ; [EQ.8]. horiz*kProjDist
- divs d1, d0 ; divide by the vertical
- addi.w #kOriginH, d0 ; add origin.h
- move.w d0, OFFSET(Point, h)(pt); save the new horizontal
- move.w #kProjDistance, d0 ; [EQ.9]
- muls gDelta.z, d0 ; height * kProjDistance
- divs d1, d0 ; divide by the vertical
- addi.w #kOriginV, d0 ; add origin.v
- move.w d0, OFFSET(Point, v)(pt); save the new vertical
- }
- }
-
- /****************************************************/
- /*
- /* For all of our cubes and pyramids, index thru each -
- /* calculate sizes, translate, rotate, check for visibility,
- /* and finally draw them.
- /*
- /****************************************************/
- static void DrawObjects(void)
- {
- register ObjectInfo *obj;
- short i;
-
- for (i = 0; i < kMaxObjects; i++)
- { obj = &gObject[i];
- gDelta.x = obj->pt3D.x - gViewer.pt3D.x; /*[EQ.3]*/
- gDelta.y = obj->pt3D.y - gViewer.pt3D.y; /*[EQ.4]*/
- gDelta.z = gViewer.pt3D.z - obj->pt3D.z ; /*[EQ.5]*/
-
- if (obj->rotates) /* does this one rotate?*/
- { gVertex[0].h=gVertex[0].v=gVertex[1].v=gVertex[3].h = -obj->halfWidth;
- gVertex[1].h=gVertex[2].h=gVertex[2].v=gVertex[3].v = obj->halfWidth;
- RotateObject(obj);
- }
- else /* translate*/
- { gVertex[0].h = gVertex[3].h = -obj->halfWidth + gDelta.x;
- gVertex[0].v = gVertex[1].v = -obj->halfWidth + gDelta.y;
- gVertex[1].h = gVertex[2].h = obj->halfWidth + gDelta.x;
- gVertex[2].v = gVertex[3].v = obj->halfWidth + gDelta.y;
- }
-
- if (obj->objType == pyramid) /* a pyramid?*/
- { gVertex[4].h = gDelta.x; /* assign apex*/
- gVertex[4].v = gDelta.y;
- }
- else
- { gVertex[4] = gVertex[0]; /* top of cube.*/
- gVertex[5] = gVertex[1];
- gVertex[6] = gVertex[2];
- gVertex[7] = gVertex[3];
- }
-
- Point2Screen(&gVertex[0]); /* rotate & plot base*/
- Point2Screen(&gVertex[1]);
- Point2Screen(&gVertex[2]);
- Point2Screen(&gVertex[3]);
- gDelta.z -= obj->height;
- Point2Screen(&gVertex[4]);
-
- if (! PtInRect(gVertex[4], &gVisRect)) /* visible?*/
- continue;
-
- QuickMoveTo(gVertex[0]);
- QuickLineTo(gVertex[1]);
- QuickLineTo(gVertex[2]);
- QuickLineTo(gVertex[3]);
- QuickLineTo(gVertex[0]);
- QuickLineTo(gVertex[4]);
-
- if (obj->objType == pyramid)
- { QuickLineTo(gVertex[1]); /* Finish pyramid.*/
- QuickMoveTo(gVertex[2]);
- QuickLineTo(gVertex[4]);
- QuickLineTo(gVertex[3]);
- } else {
- Point2Screen(&gVertex[5]); /* Finish cube.*/
- Point2Screen(&gVertex[6]);
- Point2Screen(&gVertex[7]);
- QuickLineTo(gVertex[5]);
- QuickLineTo(gVertex[6]);
- QuickLineTo(gVertex[7]);
- QuickLineTo(gVertex[4]);
- QuickMoveTo(gVertex[1]);
- QuickLineTo(gVertex[5]);
- QuickMoveTo(gVertex[2]);
- QuickLineTo(gVertex[6]);
- QuickMoveTo(gVertex[3]);
- QuickLineTo(gVertex[7]);
- } } }
-
- /****************************************************/
- /*
- /* Check mouse position (velocity is vertical movement,
- /* rotation is horiz.), calculate the sine and cosine values of
- /* the angle, and update the viewer's position. Finally, check
- /* the keyboard to see if we should move up or down.
- /*
- /****************************************************/
- static void GetViewerPosition(void)
- {
- GetMouse(&gMouse);
- if (! PtInRect(gMouse, &gWindowRect))
- return;
- gVelocity = -(gMouse.v-(kOriginV+45))/5;
- gViewer.angle += (gMouse.h-(kOriginH+6))/14;
- GetTrigValues(&gViewer.angle, &gViewer.sine, &gViewer.cosine);
-
- gViewer.pt3D.x += gViewer.sine*gVelocity/1000; /*[EQ.1]*/
- gViewer.pt3D.y += gViewer.cosine*gVelocity/1000; /*[EQ.2]*/
-
- GetKeys(&gKeys);
- if (gKeys[0] == kMoveUpKey)
- gViewer.pt3D.z += 5;
- if (gKeys[0] == kMoveDnKey)
- gViewer.pt3D.z -= 5;
- }
-
- /****************************************************/
- /*
- /* Draw a simple crosshair at the center of the window.
- /*
- /****************************************************/
- static void DrawCrossHair(void)
- {
- QuickMoveTo(#0x008200fa); /*ie., MoveTo(250, 130)*/
- QuickLineTo(#0x009600fa); /*ie., LineTo(250, 150)*/
- QuickMoveTo(#0x008c00f0); /*ie., MoveTo(240, 140)*/
- QuickLineTo(#0x008c0104); /*ie., LineTo(260, 140)*/
- }
-
- /****************************************************/
- /*
- /* Main event loop - initialize & cycle until the mouse
- /* button is pressed.
- /*
- /****************************************************/
- void main(void)
- {
- Initialize();
- while (! Button())
- { FillRect(&gBitMap.bounds, black);
- GetViewerPosition();
- DrawObjects(); /* main pipeline*/
- DrawCrossHair();
- CopyBits(&gBitMap, &gWindow->portBits, &gBitMap.bounds, &gBitMap.bounds, 0, 0L);
- }
- FlushEvents(mDownMask+keyDownMask, 0);
- }